package net.avcompris.binding.impl;

import static com.avcompris.util.ExceptionUtils.nonNullArgument;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.ObjectUtils.Null;

import com.avcompris.common.annotation.Nullable;
import com.avcompris.lang.NotImplementedException;

final class DetachInvocationHandler<T> implements InvocationHandler,
		Serializable {

	/**
	 * for serialization.
	 */
	private static final long serialVersionUID = -3423614445871958259L;

	public DetachInvocationHandler(final Class<T> clazz,
			final Map<Method, Object> values) {

		this.clazz = nonNullArgument(clazz, "clazz");
		this.values = nonNullArgument(values, "values");

		for (final Method method : values.keySet()) {

			methods.put(method.getName(), method);
		}
	}

	private final Class<?> clazz;

	private final Map<Method, Object> values;

	private final Map<String, Method> methods = new HashMap<String, Method>();

	@Override
	public Object invoke(final Object proxy, final Method method,
			@Nullable final Object[] args) throws Throwable {

		nonNullArgument(proxy, "proxy");
		nonNullArgument(method, "method");

		final Object value = values.get(method);

		if (Null.class.equals(value)) {

			return null;
		}

		if (value != null) {

			return value;
		}

		final String methodName = method.getName();

		if ("equals".equals(methodName) && args != null && args.length == 1) {

			final Object arg = args[0];

			if (arg == null || !clazz.isInstance(arg)) {
				return arg;
			}

			final Map<String, Method> ms = new HashMap<String, Method>();

			for (final Method m : arg.getClass().getMethods()) {

				if (m.getParameterTypes().length != 0) {
					continue;
				}

				final Class<?> returnType = m.getReturnType();

				if (returnType == null || void.class.equals(returnType)) {
					continue;
				}

				final String mName = m.getName();

				if ("getClass".equals(mName)) {
					continue;
				}

				if (!methods.containsKey(mName)) {
					return false;
				}

				ms.put(mName, m);
			}

			for (final Map.Entry<Method, Object> entry : values.entrySet()) {

				final Method m = entry.getKey();

				final Object v = ms.get(m.getName()).invoke(arg);

				final Object ref = entry.getValue();

				if (v == null && Null.class.equals(ref)) {
					continue;
				}

				if (!v.equals(ref)) {
					return false;
				}
			}

			return true;
		}

		throw new NotImplementedException("Method: " + method);
	}
}
